import { defineComponent, App, computed, ref, reactive, watch, cloneVNode, PropType, DefineComponent, FunctionalComponent, watchEffect } from 'vue'
import { EMPTY_ARR } from '@vue/shared'
import { useElementSize, useResizeObserver } from '@vueuse/core'
import { getSlot } from '../_util/props-util'
import ensureRange from '../_util/ensureRange'
import ScrollBar from './ScrollBar'

const prefixCls = 'x-virtual-list'

const VirtualList = defineComponent({
  name: prefixCls,
  props: {
    items: Array,
    itemKey: [String, Function] as PropType<string | ((item) => string)>,
    itemsWrap: [Object, Function] as PropType<DefineComponent | FunctionalComponent>,
    virtual: Boolean,
    bufferSize: { type: Number, default: 0 },
    onScroll: Function as PropType<(e: Event) => void>
  },
  emits: ['scroll'],
  setup(props, { expose, emit }) {
    // ================================= 计算第一行大小 =================================
    const firstNode = ref<HTMLElement>()
    const ih = ref(0)
    useResizeObserver(firstNode, ([entry]) => (ih.value = (entry.target as HTMLElement).offsetHeight))

    // ================================= MISC =================================
    const list = computed(() => props.items || EMPTY_ARR)
    const el = ref<HTMLDivElement>()
    const h = useElementSize(el).height
    const barRef = ref<DefineComponent>()
    const state = reactive({
      si: 0,
      ei: Math.min(1, list.value.length)
    })
    const showList = computed(() => list.value.slice(state.si, state.ei))
    // scrollHeight
    const sh = computed(() => Math.max(list.value.length * ih.value, h.value))
    // scrollTop
    const st = ref(0)

    function getIndex(index: number) {
      return state.si + index
    }

    // ================================= Item Key =============================
    function getKey(item: Record<string, any>, index?: number) {
      if (typeof props.itemKey === 'function') return props.itemKey(item)
      return item[props.itemKey] ?? index
    }

    // ================================= Scroll ===============================
    watchEffect(() => {
      if (!props.virtual) {
        state.si = 0
        state.ei = list.value.length
        return
      }
      const scrollTop = st.value
      const si = ~~(scrollTop / ih.value)
      const ei = Math.ceil((scrollTop + h.value) / ih.value)
      state.si = Math.max(si - props.bufferSize, 0)
      state.ei = ih.value === 0 ? si + 1 : Math.min(ei + props.bufferSize, list.value.length) // 如果height == 0，则只显示一条
    })

    // ========================================================================
    function scroll(top: number) {
      top = ensureRange(top, 0, sh.value - h.value)
      st.value = el.value.scrollTop = top
      // if (barRef.value) barRef.value.setScrollTop(top)
    }
    function onScroll(e: Event) {
      const { scrollTop } = e.currentTarget as Element
      scroll(scrollTop)
      emit('scroll', e)
    }

    // ================================= Render ===============================
    return vm => {
      const { virtual, itemsWrap } = props

      const comStyle = virtual ? `overflow-y:overlay; overflow-anchor:none;` : 'overflow:overlay;'

      const childRender = (item, index) => getSlot(vm, 'default', { data: showList.value, item, index: getIndex(index) })[0]

      const itemsNode = showList.value.map((item, index) => cloneVNode(childRender(item, index), { key: getKey(item, getIndex(index)), class: `${prefixCls}_item`, ref: index == 0 ? firstNode : undefined }))

      let wrapNode = itemsWrap ? <itemsWrap>{itemsNode}</itemsWrap> : <div style='display:flex; flex-direction:column; flex-wrap:wrap;'>{itemsNode}</div>

      wrapNode = cloneVNode(wrapNode, {
        style: `padding-top:${state.si * ih.value}px; position:relative; ${props.virtual ? `height:${sh.value}px;` : ''} box-sizing:border-box;`
      })

      return (
        <div ref={el} class={`${prefixCls}_wrapper`} style={comStyle} onScroll={onScroll}>
          {wrapNode}
          {/* <ScrollBar ref={barRef} prefixCls={prefixCls} height={props.height} scrollHeight={scrollHeight.value} onScroll={scrollTo} /> */}
        </div>
        // <ScrollBar prefixCls={prefixCls} height={props.height} scrollHeight={scrollHeight.value} scrollTop={state.scrollTop} onScroll={scrollTo} />
      )
    }
  }
})

VirtualList.install = (app: App) => {
  app.component(VirtualList.name, VirtualList)
}

export default VirtualList
